module vlib.ev.fcgi.Request ;

import vlib.Types;

private {
	extern(C){
		int fclose(int);
	}
	extern(Windows) {
                int connect(int, vAddr4*, int) ;
		int socket(int af, int type, int protocol);
        }
}

struct vFcgReq {
	alias typeof(*this) This ;
	alias typeof(this) pThis ;
	
	static void*	_root ;
	static This[]	_list ;
	static int		_len = 4 ;
	static Mutex	_mux ;
	static void Init() {
		_mux	= new Mutex ;
		expand(4) ;
	}
	
	static void expand(int i) {
		if( i < _len ) {
			return ;
		}
		_len	= _len * 2 ;
		if( _len < i ) {
			_len	= i  ;
		}
		int size	= This.sizeof * _len ;
		_root	= GC.realloc (_root,  size , GC.BlkAttr.NO_MOVE | GC.BlkAttr.NO_SCAN ) ;
		_list	= cast(This[]) _root[0..size] ;
		assert(_list.length is _len ) ;
	}
	
	static void accept(ref Berkeley ssoc){
		_mux.lock();
		scope(exit) _mux.unlock() ;
		pThis _this	= null ;
		foreach(ref q; _list){
			if( !q._running ){
				_this	= &q ;
				break ;
			}	
		}
		if( _this is null ) {
			int len	= _list.length ;
			expand(len + 1 ) ;
			_this	= &_list[len] ;
		}
		_this.Setup ;
		_this._running	= true ;
		ssoc.accept(_this.si) ;
		assert( _this._res !is null);
		_this._res.start() ;
	}
	
	enum State {
		None ,
		Connecting ,
		Proxying ,
		Closing ,
	}
	enum Errno {
		None ,
		Connect ,
		Res_Read,
		Res_Write,
		Req_Read,
		Req_Write,
	}
	
	Thread		_req, _res ;
	Semaphore	 _sem = null ;
	bool		 	_running ;
	Berkeley		si ;
	Berkeley		so ;
	vProcess*		php ;
	State		_state ;
	Errno		_errno  ;
	
	void Setup() {
		if( _sem !is null ) {
			return ;
		}
		_req		= new Thread(&Req_Loop) ;
		_res		= new Thread(&Res_Loop) ;
		_sem	= new Semaphore ;
	}
	
	void Free(){
		so.detach ;
		si.detach ;
		_mux.lock();
		scope(exit) _mux.unlock() ;
		_running	= false ;
	}
	
	void Req_Loop(){
		scope(exit){
			log!(__FILE__, __LINE__)("exit Req_Loop :{:x}", cast(void*)Thread.getThis) ;
		}
		
		log!(__FILE__, __LINE__)("connected , start req proxy:{:x}", cast(void*)Thread.getThis) ;
		Thread.sleep(.1);
		ubyte[512] tmp ;
		
		while(true){
			int i	= so.receive(tmp) ;
			if( i <= 0 || i > tmp.length ) {
				_errno	= Errno.Res_Read ;
				log!(__FILE__, __LINE__)("Res_Read: {}", i ) ;
				return ;
			}
			ubyte[] _tmp	= tmp[0..i]; 
			while( _tmp.length ) {
				int j	= si.send( _tmp) ;
				if( j < 0 || j > _tmp.length ) {
					_errno	= Errno.Req_Write ;
					log!(__FILE__, __LINE__)("Req_Write: {}", j ) ;
					return ;
				}
				if( j is _tmp.length ) {
					break ;
				}
				_tmp	= _tmp[ j .. $] ;
			}
		}
	}
	
	void Res_Loop(){
		scope(exit){
			Free;
			log!(__FILE__, __LINE__)("exit Res_Loop :{:x}", cast(void*)Thread.getThis) ;
		}
		_errno	= Errno.None ;
		_state	= State.Connecting ;
		so.open(AddressFamily.INET, SocketType.STREAM, ProtocolType.TCP) ;
		vProcess.find(php);
		int ret	= connect(so.handle, &php._addr, php._addr.sizeof );
		if( ret !is 0 ) {
			log!(__FILE__, __LINE__)(" connect error :{}",  ret) ;
			_state	= State.Closing ;
			_errno	= Errno.Connect ;
			Free;
			return ;
		}
		log!(__FILE__, __LINE__)("connected , start res proxy, {:x},", cast(void*)Thread.getThis) ;

		ubyte[512] tmp ;
		while(true) {
			int i	= si.receive(tmp) ;
			if( i <= 0 || i > tmp.length ) {
				_errno	= Errno.Req_Read ;
				log!(__FILE__, __LINE__)("Req_Read: {}", i ) ;
				return ;
			}
			ubyte[] _tmp	= tmp[0..i]; 
			while( _tmp.length ) {
				int j	= so.send( _tmp) ;
				if( j < 0 || j > _tmp.length ) {
					_errno	= Errno.Res_Write ;
					log!(__FILE__, __LINE__)("Res_Write: {}", j ) ;
					return ;
				}
				if( j is _tmp.length ) {
					break ;
				}
				_tmp	= _tmp[ j .. $] ;
			}
			
		}
	}
	
	/*
	void proxy() {
		_connected	= true ;
		if( ob is null ) {
			ob	= new vBuffer(1024, 1024);
			ib	= new vBuffer(1024, 1024);
			oo	= new vBuffer(1024, 1024);
		} else {
			ob.clear ;
			ib.clear ;
			oo.clear ;
		}
		so.open(AddressFamily.INET, SocketType.STREAM, ProtocolType.TCP) ;
		si.blocking( false ) ;
		so.blocking( false ) ;
		
		si_fd		= osf_open(cast(void*) si.handle) ;
		so_fd	= osf_open(cast(void*) so.handle) ;
		
		static int php_i	= 0 ;
		php	= &vProcess.list [  php_i ++ %  vProcess.list.length ] ;
		
		io_timer.data	= this ;
		i_read.data	= this ;
		i_write.data	= this ;
		o_read.data	= this ;
		o_write.data	= this ;
		
		ev_timer_init(&io_timer, &On_Timer,  6, 6) ;
		ev_io_init(&i_read, &On_Req_Read,  si_fd, EV_READ) ;
		ev_io_init(&i_write, &On_Req_Write,  si_fd, EV_WRITE ) ;
		
		ev_io_init(&o_read, &On_Res_Read,  so_fd, EV_READ) ;
		ev_io_init(&o_write, &On_Res_Write,  so_fd, EV_WRITE ) ;
		
		// ev_io_set(&io_res, si_fd, EV_READ) ;
		
		int ret = connect(so.handle, &php._addr, php._addr.sizeof ) ;
		ev_timer_start(vLib.loop, &io_timer);
		ev_io_start(vLib.loop, &i_read);
		ev_io_start(vLib.loop, &o_write);
		_state	= State.Connecting ;
	}
	
	void close() {
		ev_timer_stop(vLib.loop, &io_timer);

		ev_io_stop(vLib.loop, &i_read);
		ev_io_stop(vLib.loop, &i_write);
		ev_io_stop(vLib.loop, &o_read);
		ev_io_stop(vLib.loop, &o_write);
		
		osf_close(si_fd);
		osf_close(so_fd);
		so.detach ;
		si.detach ;
		_state		= State.Closing ;
		_connected	= false ;
	}
	
	
	static extern(C) void On_Timer(ev_loop_t* loop, ev_timer* w, int ev) {
		pThis	_this	= cast(pThis) w.data ;
		if( _this._state is State.Connecting  ) {
			// restart php 
		}
		log!(__FILE__, __LINE__)("On_Timer");
		_this.php.reboot() ;
		_this.close() ;
		return ;
	}
	
	static extern(C) void On_Req_Read (ev_loop_t* loop, ev_io* w, int ev) {
		pThis	_this	= cast(pThis) w.data ;
		log!(__FILE__, __LINE__)("On_Req_Read start, len:{}", _this.ib.readable);
		bool	is_first ;
		ubyte[512] tmp ;
		while( true ) {
			int i	= _this.si.receive(tmp);
			if( i < 0 || i > tmp.length ) {
				log!(__FILE__, __LINE__)("read:{}:`{}`", i, _this.ib.readable );
				break ;
			}
			if( is_first ) {
				is_first	= false ;
				if( i is 0 ) {
					log!(__FILE__, __LINE__)("close, read:`{}`", i, _this.ib.readable );
					_this.close() ;
					return ;
				}
			}
			if( i is 0 ) {
				break ;
			}
			_this.ib(tmp[0..i]) ;
		}
		log!(__FILE__, __LINE__)("On_Req_Read end, len:{}", _this.ib.readable);
		if( _this.ib.readable ) {
			ev_io_start(vLib.loop, &_this.o_write) ;
		}
		ev_timer_stop(vLib.loop, &_this.io_timer);
		ev_timer_start(vLib.loop, &_this.io_timer);
	}
	
	static extern(C) void On_Req_Write(ev_loop_t* loop, ev_io* w, int ev) {
		pThis	_this	= cast(pThis) w.data ;
		log!(__FILE__, __LINE__)("On_Req_Write start, len:{}",  _this.ob.readable );
		if( _this.ob.readable is 0 ) {
			ev_io_stop(vLib.loop, &_this.i_write) ;
			return ;
		}
		bool	is_write	= false ;
		ubyte[] _tmp	= cast(ubyte[]) _this.ob.slice ;
		while(true) {
			int i	= _this.si.send(  _tmp ) ;
			if( i > _tmp.length || i < 0 ) {
				log!(__FILE__, __LINE__)("close");
				_this.php.reboot() ;
				_this.close() ;
				return ;
			}
			if( i is 0 ) {
				break ;
			}
			is_write	= true ;
			if(  i is _tmp.length ) {
				_tmp	= _tmp[0..0] ;
				break ;
			}
			_tmp	= _tmp[i..$] ;
		}
		log!(__FILE__, __LINE__)("On_Req_Write end:`{}:{}`", _this.ob.readable - _tmp.length,  _tmp.length );
		_this.ob.clear;
		if( _tmp.length ) {
			_this.oo(_tmp);
			_this.ob( _this.oo.slice ) ;
			_this.oo.clear ;
		} else {
			ev_io_stop(vLib.loop, &_this.i_write) ;
		}
		if( is_write ) {
			ev_io_stop(vLib.loop, &_this.o_read) ;
		}
		ev_timer_stop(vLib.loop, &_this.io_timer);
		ev_timer_start(vLib.loop, &_this.io_timer);
	}
	
	static extern(C) void On_Res_Read (ev_loop_t* loop, ev_io* w, int ev) {
		pThis	_this	= cast(pThis) w.data ;
		log!(__FILE__, __LINE__)("On_Res_Read start, len:{}", _this.ob.readable);
		bool	is_first ;
		ubyte[512] tmp ;
		while( true ) {
			int i	= _this.so.receive(tmp);
			if( i < 0 || i > tmp.length ) {
				ev_io_stop(vLib.loop, &_this.o_read) ;
				//ev_io_start(vLib.loop, &_this.i_read) ;
				log!(__FILE__, __LINE__)("read:{}:{}", i, _this.ob.readable );
				break ;
			}
			if( is_first ) {
				is_first	= false ;
				if( i is 0 ) {
					log!(__FILE__, __LINE__)("close");
					_this.php.reboot() ;
					_this.close() ;
					return ;
				}
			}
			if( i is 0 ) {
				break ;
			}
			_this.ob(tmp[0..i]) ;
		}
		log!(__FILE__, __LINE__)("On_Res_Read end, {}",  _this.ob.readable );
		if( _this.ob.readable ) {
			ev_io_stop(vLib.loop, &_this.o_read) ;
			ev_io_start(vLib.loop, &_this.i_write) ;
		}
		ev_timer_stop(vLib.loop, &_this.io_timer);
		ev_timer_start(vLib.loop, &_this.io_timer);
	}
	
	static extern(C) void On_Res_Write(ev_loop_t* loop, ev_io* w, int ev) {
		pThis	_this	= cast(pThis) w.data ;
		log!(__FILE__, __LINE__)("On_Res_Write start, len:{}", _this.ib.readable);
		if( _this._state is State.Connecting  ) {
			_this._state	= State.Proxying ;
		}
		if( _this.ib.readable is 0 ) {
			ev_io_stop(vLib.loop, &_this.o_write) ;
			return ;
		}
		ubyte[] _tmp	= cast(ubyte[]) _this.ib.slice ;
		while(true) {
			int i	= _this.so.send(  _tmp ) ;
			if( i > _tmp.length || i < 0 ) {
				log!(__FILE__, __LINE__)("close");
				_this.php.reboot() ;
				_this.close() ;
				return ;
			}
			if(  i is _tmp.length ) {
				_tmp	= _tmp[0..0] ;
				break ;
			}
			if( i is 0 || i is _tmp.length ) {
				break ;
			}
			_tmp	= _tmp[i..$] ;
		}
		log!(__FILE__, __LINE__)("On_Res_Write end:`{}:{}`", _this.ib.readable - _tmp.length,  _tmp.length );
		_this.ib.clear;
		if( _tmp.length ) {
			_this.oo(_tmp);
			_this.ib( _this.oo.slice ) ;
			_this.oo.clear ;
		} else {
			ev_io_stop(vLib.loop, &_this.o_write) ;
			ev_io_start(vLib.loop, &_this.o_read) ;
		}
		
		ev_timer_stop(vLib.loop, &_this.io_timer);
		ev_timer_start(vLib.loop, &_this.io_timer);
	}
	*/
} 